using HBase;
using hconnect;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

namespace Dapper {
    [AssemblyNeutral, AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct ,AllowMultiple = false ,Inherited = false)]
    internal sealed class AssemblyNeutralAttribute :Attribute {
    }
    public static partial class SqlMapper {
        /// <summary>
        /// Implement this interface to pass an arbitrary db specific set of parameters to Dapper
        /// </summary>
        public partial interface IDynamicParameters {
            /// <summary>
            /// Add all the parameters needed to the command just before it executes
            /// </summary>
            /// <param name="command">The raw command prior to execution</param>
            /// <param name="identity">Information about the query</param>
            void AddParameters(IDbCommand command ,Identity identity);
        }
        /// <summary>
        /// Extends IDynamicParameters providing by-name lookup of parameter values
        /// </summary>
        public interface IParameterLookup :IDynamicParameters {
            /// <summary>
            /// Get the value of the specified parameter (return null if not found)
            /// </summary>
            object this[string name] { get; }
        }
        /// <summary>
        /// Extends IDynamicParameters with facilities for executing callbacks after commands have completed
        /// </summary>
        public partial interface IParameterCallbacks :IDynamicParameters {
            /// <summary>
            /// Invoked when the command has executed
            /// </summary>
            void OnCompleted();
        }
        /// <summary>
        /// Implement this interface to pass an arbitrary db specific parameter to Dapper
        /// </summary>
        [AssemblyNeutral]
        public interface ICustomQueryParameter {
            /// <summary>
            /// Add the parameter needed to the command before it executes
            /// </summary>
            /// <param name="command">The raw command prior to execution</param>
            /// <param name="name">Parameter name</param>
            void AddParameter(IDbCommand command ,string name);
        }
        /// <summary>
        /// Implement this interface to perform custom type-based parameter handling and value parsing
        /// </summary>
        [AssemblyNeutral]
        public interface ITypeHandler {
            /// <summary>
            /// Assign the value of a parameter before a command executes
            /// </summary>
            /// <param name="parameter">The parameter to configure</param>
            /// <param name="value">Parameter value</param>
            void SetValue(IDbDataParameter parameter ,object value);
            /// <summary>
            /// Parse a database value back to a typed value
            /// </summary>
            /// <param name="value">The value from the database</param>
            /// <param name="destinationType">The type to parse to</param>
            /// <returns>The typed value</returns>
            object Parse(Type destinationType ,object value);
        }
        /// <summary>
        /// Base-class for simple type-handlers
        /// </summary>
        public abstract class TypeHandler<T> :ITypeHandler {
            /// <summary>
            /// Assign the value of a parameter before a command executes
            /// </summary>
            /// <param name="parameter">The parameter to configure</param>
            /// <param name="value">Parameter value</param>
            public abstract void SetValue(IDbDataParameter parameter ,T value);
            /// <summary>
            /// Parse a database value back to a typed value
            /// </summary>
            /// <param name="value">The value from the database</param>
            /// <returns>The typed value</returns>
            public abstract T Parse(object value);
            void ITypeHandler.SetValue(IDbDataParameter parameter ,object value) {
                if( value is DBNull ) {
                    parameter.Value = value;
                } else {
                    SetValue(parameter ,( T ) value);
                }
            }
            object ITypeHandler.Parse(Type destinationType ,object value) {
                return Parse(value);
            }
        }
        /// <summary>
        /// Implement this interface to change default mapping of reader columns to type members
        /// </summary>
        public interface ITypeMap {
            /// <summary>
            /// Finds best constructor
            /// </summary>
            /// <param name="names">DataReader column names</param>
            /// <param name="types">DataReader column types</param>
            /// <returns>Matching constructor or default one</returns>
            ConstructorInfo FindConstructor(string[] names ,Type[] types);
            /// <summary>
            /// Returns a constructor which should *always* be used.
            /// 
            /// Parameters will be default values, nulls for reference types and zero'd for value types.
            /// 
            /// Use this class to force object creation away from parameterless constructors you don't control.
            /// </summary>
            ConstructorInfo FindExplicitConstructor();
            /// <summary>
            /// Gets mapping for constructor parameter
            /// </summary>
            /// <param name="constructor">Constructor to resolve</param>
            /// <param name="columnName">DataReader column name</param>
            /// <returns>Mapping implementation</returns>
            IMemberMap GetConstructorParameter(ConstructorInfo constructor ,string columnName);
            /// <summary>
            /// Gets member mapping for column
            /// </summary>
            /// <param name="columnName">DataReader column name</param>
            /// <returns>Mapping implementation</returns>
            IMemberMap GetMember(string columnName);
        }
        /// <summary>
        /// Implements this interface to provide custom member mapping
        /// </summary>
        public interface IMemberMap {
            /// <summary>
            /// Source DataReader column name
            /// </summary>
            string ColumnName {
                get;
            }
            /// <summary>
            ///  Target member type
            /// </summary>
            Type MemberType {
                get;
            }
            /// <summary>
            /// Target property
            /// </summary>
            PropertyInfo Property {
                get;
            }
            /// <summary>
            /// Target field
            /// </summary>
            FieldInfo Field {
                get;
            }
            /// <summary>
            /// Target constructor parameter
            /// </summary>
            ParameterInfo Parameter {
                get;
            }
        }
        /// <summary>
        /// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example),
        /// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE**
        /// equality. The type is fully thread-safe.
        /// </summary>
        internal partial class Link<TKey, TValue> where TKey : class {
            public static bool TryGet(Link<TKey ,TValue> link ,TKey key ,out TValue value) {
                while( link != null ) {
                    if( ( object ) key == ( object ) link.Key ) {
                        value = link.Value;
                        return true;
                    }
                    link = link.Tail;
                }
                value = default(TValue);
                return false;
            }
            public static bool TryAdd(ref Link<TKey ,TValue> head ,TKey key ,ref TValue value) {
                bool tryAgain;
                do {
                    var snapshot = Interlocked.CompareExchange(ref head ,null ,null);
                    TValue found;
                    if( TryGet(snapshot ,key ,out found) ) { // existing match; report the existing value instead
                        value = found;
                        return false;
                    }
                    var newNode = new Link<TKey ,TValue>(key ,value ,snapshot);
                    // did somebody move our cheese?
                    tryAgain = Interlocked.CompareExchange(ref head ,newNode ,snapshot) != snapshot;
                } while( tryAgain );
                return true;
            }
            private Link(TKey key ,TValue value ,Link<TKey ,TValue> tail) {
                Key = key;
                Value = value;
                Tail = tail;
            }
            public TKey Key {
                get; private set;
            }
            public TValue Value {
                get; private set;
            }
            public Link<TKey ,TValue> Tail {
                get; private set;
            }
        }
        partial class CacheInfo {
            public DeserializerState Deserializer {
                get; set;
            }
            public Func<IDataReader ,object>[] OtherDeserializers {
                get; set;
            }
            public Action<IDbCommand ,object> ParamReader {
                get; set;
            }
            private int hitCount;
            public int GetHitCount() {
                return Interlocked.CompareExchange(ref hitCount ,0 ,0);
            }
            public void RecordHit() {
                Interlocked.Increment(ref hitCount);
            }
        }
        static int GetColumnHash(IDataReader reader) {
            unchecked {
                int colCount = reader.FieldCount, hash = colCount;
                for( int i = 0;i < colCount;i++ ) {   // binding code is only interested in names - not types
                    object tmp = reader.GetName(i);
                    hash = (hash * 31) + (tmp == null ? 0 : tmp.GetHashCode());
                }
                return hash;
            }
        }
        struct DeserializerState {
            public readonly int Hash;
            public readonly Func<IDataReader ,object> Func;
            public DeserializerState(int hash ,Func<IDataReader ,object> func) {
                Hash = hash;
                Func = func;
            }
        }
       
        
        private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0;
      
       
        /// <summary>
        /// Purge the query cache 
        /// </summary>
       
        
       
        static Dictionary<Type ,DbType> typeMap;
        static SqlMapper() {
            typeMap = new Dictionary<Type ,DbType>();
            typeMap[typeof(byte)] = DbType.Byte;
            typeMap[typeof(sbyte)] = DbType.SByte;
            typeMap[typeof(short)] = DbType.Int16;
            typeMap[typeof(ushort)] = DbType.UInt16;
            typeMap[typeof(int)] = DbType.Int32;
            typeMap[typeof(uint)] = DbType.UInt32;
            typeMap[typeof(long)] = DbType.Int64;
            typeMap[typeof(ulong)] = DbType.UInt64;
            typeMap[typeof(float)] = DbType.Single;
            typeMap[typeof(double)] = DbType.Double;
            typeMap[typeof(decimal)] = DbType.Decimal;
            typeMap[typeof(bool)] = DbType.Boolean;
            typeMap[typeof(string)] = DbType.String;
            typeMap[typeof(char)] = DbType.StringFixedLength;
            typeMap[typeof(Guid)] = DbType.Guid;
            typeMap[typeof(DateTime)] = DbType.DateTime;
            typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
            typeMap[typeof(TimeSpan)] = DbType.Time;
            typeMap[typeof(byte[])] = DbType.Binary;
            typeMap[typeof(byte?)] = DbType.Byte;
            typeMap[typeof(sbyte?)] = DbType.SByte;
            typeMap[typeof(short?)] = DbType.Int16;
            typeMap[typeof(ushort?)] = DbType.UInt16;
            typeMap[typeof(int?)] = DbType.Int32;
            typeMap[typeof(uint?)] = DbType.UInt32;
            typeMap[typeof(long?)] = DbType.Int64;
            typeMap[typeof(ulong?)] = DbType.UInt64;
            typeMap[typeof(float?)] = DbType.Single;
            typeMap[typeof(double?)] = DbType.Double;
            typeMap[typeof(decimal?)] = DbType.Decimal;
            typeMap[typeof(bool?)] = DbType.Boolean;
            typeMap[typeof(char?)] = DbType.StringFixedLength;
            typeMap[typeof(Guid?)] = DbType.Guid;
            typeMap[typeof(DateTime?)] = DbType.DateTime;
            typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
            typeMap[typeof(TimeSpan?)] = DbType.Time;
            typeMap[typeof(object)] = DbType.Object;
        }
        
       
        /// <summary>
        /// Not intended for direct usage
        /// </summary>
        [Obsolete("Not intended for direct usage" ,false)]
#if !DNXCORE50
        [Browsable(false)]
#endif
        [EditorBrowsable(EditorBrowsableState.Never)]
        public static class TypeHandlerCache<T> {
            /// <summary>
            /// Not intended for direct usage
            /// </summary>
            [Obsolete("Not intended for direct usage" ,true)]
            public static T Parse(object value) {
                return ( T ) handler.Parse(typeof(T) ,value);
            }
            /// <summary>
            /// Not intended for direct usage
            /// </summary>
            [Obsolete("Not intended for direct usage" ,true)]
            public static void SetValue(IDbDataParameter parameter ,object value) {
                handler.SetValue(parameter ,value);
            }
            internal static void SetHandler(ITypeHandler handler) {
#pragma warning disable 618
                TypeHandlerCache<T>.handler = handler;
#pragma warning restore 618
            }
            private static ITypeHandler handler;
        }
        
        internal const string LinqBinary = "System.Data.Linq.Binary";
      
        internal static DbType LookupDbType(Type type ,string name ,bool demand ,out ITypeHandler handler) {
            DbType dbType;
            handler = null;
            var nullUnderlyingType = Nullable.GetUnderlyingType(type);
            if( nullUnderlyingType != null )
                type = nullUnderlyingType;
            if( type.IsEnum() && !typeMap.ContainsKey(type) ) {
                type = Enum.GetUnderlyingType(type);
            }
            if( typeMap.TryGetValue(type ,out dbType) ) {
                return dbType;
            }
            if( type.FullName == LinqBinary ) {
                return DbType.Binary;
            }
            if( typeof(IEnumerable).IsAssignableFrom(type) ) {
                return DynamicParameters.EnumerableMultiParameter;
            }
            
            if( demand )
                throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value" ,name ,type.FullName));
            return DbType.Object;
        }
        /// <summary>
        /// Identity of a cached query in Dapper, used for extensibility
        /// </summary>
        public partial class Identity :IEquatable<Identity> {
            internal Identity ForGrid(Type primaryType ,int gridIndex) {
                return new Identity(sql ,commandType ,connectionString ,primaryType ,parametersType ,null ,gridIndex);
            }
           
            /// <summary>
            /// Create an identity for use with DynamicParameters, internal use only
            /// </summary>
            /// <param name="type"></param>
            /// <returns></returns>
            public Identity ForDynamicParameters(Type type) {
                return new Identity(sql ,commandType ,connectionString ,this.type ,type ,null ,-1);
            }
            internal Identity(string sql ,CommandType? commandType ,IDbConnection connection ,Type type ,Type parametersType ,Type[] otherTypes)
                : this(sql ,commandType ,connection.ConnectionString ,type ,parametersType ,otherTypes ,0) {
            }
            private Identity(string sql ,CommandType? commandType ,string connectionString ,Type type ,Type parametersType ,Type[] otherTypes ,int gridIndex) {
                this.sql = sql;
                this.commandType = commandType;
                this.connectionString = connectionString;
                this.type = type;
                this.parametersType = parametersType;
                this.gridIndex = gridIndex;
                unchecked {
                    hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this
                    hashCode = hashCode * 23 + commandType.GetHashCode();
                    hashCode = hashCode * 23 + gridIndex.GetHashCode();
                    hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode());
                    hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode());
                    if( otherTypes != null ) {
                        foreach( var t in otherTypes ) {
                            hashCode = hashCode * 23 + (t == null ? 0 : t.GetHashCode());
                        }
                    }
                    hashCode = hashCode * 23 + (connectionString == null ? 0 : SqlMapper.connectionStringComparer.GetHashCode(connectionString));
                    hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode());
                }
            }
            /// <summary>
            /// 
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public override bool Equals(object obj) {
                return Equals(obj as Identity);
            }
            /// <summary>
            /// The sql
            /// </summary>
            public readonly string sql;
            /// <summary>
            /// The command type 
            /// </summary>
            public readonly CommandType? commandType;
            /// <summary>
            /// 
            /// </summary>
            public readonly int hashCode, gridIndex;
            /// <summary>
            /// 
            /// </summary>
            public readonly Type type;
            /// <summary>
            /// 
            /// </summary>
            public readonly string connectionString;
            /// <summary>
            /// 
            /// </summary>
            public readonly Type parametersType;
            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode() {
                return hashCode;
            }
            /// <summary>
            /// Compare 2 Identity objects
            /// </summary>
            /// <param name="other"></param>
            /// <returns></returns>
            public bool Equals(Identity other) {
                return
                    other != null &&
                    gridIndex == other.gridIndex &&
                    type == other.type &&
                    sql == other.sql &&
                    commandType == other.commandType &&
                    SqlMapper.connectionStringComparer.Equals(connectionString ,other.connectionString) &&
                    parametersType == other.parametersType;
            }
        }
        /// <summary>
        /// Obtains the data as a list; if it is *already* a list, the original object is returned without
        /// any duplication; otherwise, ToList() is invoked.
        /// </summary>
        public static List<T> AsList<T>(this IEnumerable<T> source) {
            return (source == null || source is List<T>) ? ( List<T> ) source : source.ToList();
        }
        /// <summary>
        /// Execute parameterized SQL  
        /// </summary>
        /// <returns>Number of rows affected</returns>
        public static int Execute(this IDbConnection cnn ,string sql ,object param = null ,IDbTransaction transaction = null ,int? commandTimeout = null ,CommandType? commandType = null) {
            sql = sql2dialect(cnn ,sql);
            var command = new CommandDefinition(sql ,( object ) param ,transaction ,commandTimeout ,commandType  );
            return ExecuteImpl(cnn ,ref command);
        }
     
        /// <summary>
        /// Execute parameterized SQL that selects a single value
        /// </summary>
        /// <returns>The first cell selected</returns>
        public static T ExecuteScalar<T>(
this IDbConnection cnn ,string sql ,object param = null ,IDbTransaction transaction = null ,int? commandTimeout = null ,CommandType? commandType = null
) {
            sql = sql2dialect(cnn ,sql);
            var command = new CommandDefinition(sql ,( object ) param ,transaction ,commandTimeout ,commandType  );
            return ExecuteScalarImpl<T>(cnn ,ref command);
        }
        
     
        private static IEnumerable GetMultiExec(object param) {
            return (param is IEnumerable && !(param is string || param is IEnumerable<KeyValuePair<string ,object>>
                    )) ? ( IEnumerable ) param : null;
        }
        /// <summary>
        /// Return a list of dynamic objects, reader is closed after the call
        /// </summary>
        /// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
        public static IEnumerable<dynamic> Query(this IDbConnection cnn ,string sql ,object param = null ,IDbTransaction transaction = null ,int? commandTimeout = null ,CommandType? commandType = null) {
            return Query<DapperRow>(cnn ,sql ,param as object ,transaction  ,commandTimeout ,commandType);
        }
        public static String sql2dialect(IDbConnection conn ,String sql) {
            if( conn.GetType().Name == "OracleConnection" ) {
                sql = sql.Replace("@" ,":");
            } else if( conn.GetType().Name == "MySqlConnection" ) {

                Regex paramReg = new Regex(@"(?<!@)@\w+");
                sql = paramReg.Replace(sql ,m => "?" + m.Value.Substring(1));

                // Convert @@uservar -> @uservar and @@@systemvar -> @@systemvar
                sql = sql.Replace("@@" ,"@");
                sql = sql.Replace("[" ," ");
                sql = sql.Replace("]" ," ");

                sql = sql.ToLower().Replace("sgj_get_sys_now" ," now()");

            } else if( conn.GetType().Name == "sqlServerConnection" ) {
                sql = sql.ToLower().Replace("sgj_get_sys_now" ,"getDate()");

            }


            return sql;
        }
        /// <summary>
        /// Executes a query, returning the data typed as per T
        /// </summary>
        /// <remarks>the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object</remarks>
        /// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
        /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
        /// </returns>
        public static IEnumerable<T> Query<T>(this IDbConnection cnn ,string sql ,object param = null ,IDbTransaction transaction = null ,int? commandTimeout = null ,CommandType? commandType = null) {
            sql = sql2dialect(cnn ,sql);
            var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType);

            try {
                var data = QueryImpl<T>(cnn, command, typeof(T));
                return  data.ToList();
            } catch (Exception e){
                if (param != null) {
                    sql += JsonUtil.mySerializeObject(param);
                }
                throw new Exception(sql, e);
            }
        
          
        }
        public static T QueryUnique<T>(this IDbConnection cnn ,string sql ,object param = null ,IDbTransaction transaction = null ,int? commandTimeout = null ,CommandType? commandType = null) {
            IEnumerable<T> list = Query<T>(cnn ,sql ,param ,transaction ,commandTimeout ,commandType);
            int count = list.Count();
            if( count > 1 ) {
                String paramStr = param != null ? JsonUtil.mySerializeObject(param) : "";
                throw new Exception(sql + "sgjnot unique has result" + count + paramStr);
            }
            return list.FirstOrDefault();
        }
        
       
        private static CacheInfo GetCacheInfo(Identity identity ,object exampleParameters  ) {
            CacheInfo info;
       
                info = new CacheInfo();
                if( identity.parametersType != null ) {
                    Action<IDbCommand ,object> reader;
                    if( exampleParameters is IDynamicParameters ) {
                        reader = (cmd ,obj) => { (( IDynamicParameters ) obj).AddParameters(cmd ,identity); };
                    } else if( exampleParameters is IEnumerable<KeyValuePair<string ,object>> ) {
                        reader = (cmd ,obj) => {
                            IDynamicParameters mapped = new DynamicParameters(obj);
                            mapped.AddParameters(cmd ,identity);
                        };
                    } else {
                        var literals = GetLiteralTokens(identity.sql);
                        reader = CreateParamInfoGenerator(identity ,false ,true ,literals);
                    }
                    if( (identity.commandType == null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql) ) {
                        var tail = reader;
                        var sql = identity.sql;
                        reader = (cmd ,obj) => {
                            tail(cmd ,obj);
                            PassByPosition(cmd);
                        };
                    }
                    info.ParamReader = reader;
                }
                
            
            return info;
        }
        private static bool ShouldPassByPosition(string sql) {
            return sql != null && sql.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql);
        }
        private static void PassByPosition(IDbCommand cmd) {
            if( cmd.Parameters.Count == 0 )
                return;
            Dictionary<string ,IDbDataParameter> parameters = new Dictionary<string ,IDbDataParameter>(StringComparer.Ordinal);
            foreach( IDbDataParameter param in cmd.Parameters ) {
                if( !string.IsNullOrEmpty(param.ParameterName) )
                    parameters[param.ParameterName] = param;
            }
            HashSet<string> consumed = new HashSet<string>(StringComparer.Ordinal);
            bool firstMatch = true;
            cmd.CommandText = pseudoPositional.Replace(cmd.CommandText ,match => {
                string key = match.Groups[1].Value;
                IDbDataParameter param;
                if( !consumed.Add(key) ) {
                    throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once");
                } else if( parameters.TryGetValue(key ,out param) ) {
                    if( firstMatch ) {
                        firstMatch = false;
                        cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully
                    }
                    // if found, return the anonymous token "?"
                    cmd.Parameters.Add(param);
                    parameters.Remove(key);
                    consumed.Add(key);
                    return "?";
                } else {
                    // otherwise, leave alone for simple debugging
                    return match.Value;
                }
            });
        }
        private static Func<IDataReader ,object> GetDeserializer(Type type ,IDataReader reader ,int startBound ,int length ,bool returnNullIfFirstMissing) {
#if !CSHARP30
            // dynamic is passed in as Object ... by c# design
            if( type == typeof(object)
                || type == typeof(DapperRow) ) {
                return GetDapperRowDeserializer(reader ,startBound ,length ,returnNullIfFirstMissing);
            }
#else
            if (type.IsAssignableFrom(typeof(Dictionary<string, object>)))
            {
                return GetDictionaryDeserializer(reader, startBound, length, returnNullIfFirstMissing);
            }
#endif
            Type underlyingType = null;
            if( !(typeMap.ContainsKey(type) || type.IsEnum() || type.FullName == LinqBinary ||
                (type.IsValueType() && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum())) ) {
                 
                return GetTypeDeserializer(type ,reader ,startBound ,length ,returnNullIfFirstMissing);
            }
            return GetStructDeserializer(type ,underlyingType ?? type ,startBound);
        }
       
#if !CSHARP30
        private sealed partial class DapperTable {
            string[] fieldNames;
            readonly Dictionary<string ,int> fieldNameLookup;
            internal string[] FieldNames {
                get {
                    return fieldNames;
                }
            }
            public DapperTable(string[] fieldNames) {
                if( fieldNames == null )
                    throw new ArgumentNullException("fieldNames");
                this.fieldNames = fieldNames;
                fieldNameLookup = new Dictionary<string ,int>(fieldNames.Length ,StringComparer.Ordinal);
                // if there are dups, we want the **first** key to be the "winner" - so iterate backwards
                for( int i = fieldNames.Length - 1;i >= 0;i-- ) {
                    string key = fieldNames[i];
                    if( key != null )
                        fieldNameLookup[key] = i;
                }
            }
            internal int IndexOfName(string name) {
                int result;
                return (name != null && fieldNameLookup.TryGetValue(name ,out result)) ? result : -1;
            }
            internal int AddField(string name) {
                if( name == null )
                    throw new ArgumentNullException("name");
                if( fieldNameLookup.ContainsKey(name) )
                    throw new InvalidOperationException("Field already exists: " + name);
                int oldLen = fieldNames.Length;
                Array.Resize(ref fieldNames ,oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case
                fieldNames[oldLen] = name;
                fieldNameLookup[name] = oldLen;
                return oldLen;
            }
             
            public int FieldCount {
                get {
                    return fieldNames.Length;
                }
            }
        }
        sealed partial class DapperRowMetaObject :System.Dynamic.DynamicMetaObject {
            static readonly MethodInfo getValueMethod = typeof(IDictionary<string ,object>).GetProperty("Item").GetGetMethod();
            static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue" ,new Type[] { typeof(string) ,typeof(object) });
            public DapperRowMetaObject(
                System.Linq.Expressions.Expression expression ,
                System.Dynamic.BindingRestrictions restrictions
                )
                : base(expression ,restrictions) {
            }
            public DapperRowMetaObject(
                System.Linq.Expressions.Expression expression ,
                System.Dynamic.BindingRestrictions restrictions ,
                object value
                )
                : base(expression ,restrictions ,value) {
            }
            System.Dynamic.DynamicMetaObject CallMethod(
                MethodInfo method ,
                System.Linq.Expressions.Expression[] parameters
                ) {
                var callMethod = new System.Dynamic.DynamicMetaObject(
                    System.Linq.Expressions.Expression.Call(
                        System.Linq.Expressions.Expression.Convert(Expression ,LimitType) ,
                        method ,
                        parameters) ,
                    System.Dynamic.BindingRestrictions.GetTypeRestriction(Expression ,LimitType)
                    );
                return callMethod;
            }
            public override System.Dynamic.DynamicMetaObject BindGetMember(System.Dynamic.GetMemberBinder binder) {
                var parameters = new System.Linq.Expressions.Expression[]
                                     {
                                         System.Linq.Expressions.Expression.Constant(binder.Name)
                                     };
                var callMethod = CallMethod(getValueMethod ,parameters);
                return callMethod;
            }
            // Needed for Visual basic dynamic support
            public override System.Dynamic.DynamicMetaObject BindInvokeMember(System.Dynamic.InvokeMemberBinder binder ,System.Dynamic.DynamicMetaObject[] args) {
                var parameters = new System.Linq.Expressions.Expression[]
                                     {
                                         System.Linq.Expressions.Expression.Constant(binder.Name)
                                     };
                var callMethod = CallMethod(getValueMethod ,parameters);
                return callMethod;
            }
            public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.SetMemberBinder binder ,System.Dynamic.DynamicMetaObject value) {
                var parameters = new System.Linq.Expressions.Expression[]
                                     {
                                         System.Linq.Expressions.Expression.Constant(binder.Name),
                                         value.Expression,
                                     };
                var callMethod = CallMethod(setValueMethod ,parameters);
                return callMethod;
            }
        }
        private sealed partial class DapperRow
            :System.Dynamic.IDynamicMetaObjectProvider
            , IDictionary<string ,object> {
            readonly DapperTable table;
            object[] values;
            public DapperRow(DapperTable table ,object[] values) {
                if( table == null )
                    throw new ArgumentNullException("table");
                if( values == null )
                    throw new ArgumentNullException("values");
                this.table = table;
                this.values = values;
            }
            private sealed class DeadValue {
                public static readonly DeadValue Default = new DeadValue();
                private DeadValue() {
                }
            }
            int ICollection<KeyValuePair<string ,object>>.Count {
                get {
                    int count = 0;
                    for( int i = 0;i < values.Length;i++ ) {
                        if( !(values[i] is DeadValue) )
                            count++;
                    }
                    return count;
                }
            }
            public bool TryGetValue(string name ,out object value) {
                var index = table.IndexOfName(name);
                if( index < 0 ) { // doesn't exist
                    value = null;
                    return false;
                }
                // exists, **even if** we don't have a value; consider table rows heterogeneous
                value = index < values.Length ? values[index] : null;
                if( value is DeadValue ) { // pretend it isn't here
                    value = null;
                    return false;
                }
                return true;
            }
            public override string ToString() {
                var sb = GetStringBuilder().Append("{DapperRow");
                foreach( var kv in this ) {
                    var value = kv.Value;
                    sb.Append(", ").Append(kv.Key);
                    if( value != null ) {
                        sb.Append(" = '").Append(kv.Value).Append('\'');
                    } else {
                        sb.Append(" = NULL");
                    }
                }
                return sb.Append('}').__ToStringRecycle();
            }
            System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
                System.Linq.Expressions.Expression parameter) {
                return new DapperRowMetaObject(parameter ,System.Dynamic.BindingRestrictions.Empty ,this);
            }
            public IEnumerator<KeyValuePair<string ,object>> GetEnumerator() {
                var names = table.FieldNames;
                for( var i = 0;i < names.Length;i++ ) {
                    object value = i < values.Length ? values[i] : null;
                    if( !(value is DeadValue) ) {
                        yield return new KeyValuePair<string ,object>(names[i] ,value);
                    }
                }
            }
            IEnumerator IEnumerable.GetEnumerator() {
                return GetEnumerator();
            }
            #region Implementation of ICollection<KeyValuePair<string,object>>
            void ICollection<KeyValuePair<string ,object>>.Add(KeyValuePair<string ,object> item) {
                IDictionary<string ,object> dic = this;
                dic.Add(item.Key ,item.Value);
            }
            void ICollection<KeyValuePair<string ,object>>.Clear() { // removes values for **this row**, but doesn't change the fundamental table
                for( int i = 0;i < values.Length;i++ )
                    values[i] = DeadValue.Default;
            }
            bool ICollection<KeyValuePair<string ,object>>.Contains(KeyValuePair<string ,object> item) {
                object value;
                return TryGetValue(item.Key ,out value) && Equals(value ,item.Value);
            }
            void ICollection<KeyValuePair<string ,object>>.CopyTo(KeyValuePair<string ,object>[] array ,int arrayIndex) {
                foreach( var kv in this ) {
                    array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault
                }
            }
            bool ICollection<KeyValuePair<string ,object>>.Remove(KeyValuePair<string ,object> item) {
                IDictionary<string ,object> dic = this;
                return dic.Remove(item.Key);
            }
            bool ICollection<KeyValuePair<string ,object>>.IsReadOnly {
                get {
                    return false;
                }
            }
            #endregion
            #region Implementation of IDictionary<string,object>
            bool IDictionary<string ,object>.ContainsKey(string key) {
                int index = table.IndexOfName(key);
                if( index < 0 || index >= values.Length || values[index] is DeadValue )
                    return false;
                return true;
            }
            void IDictionary<string ,object>.Add(string key ,object value) {
                SetValue(key ,value ,true);
            }
            bool IDictionary<string ,object>.Remove(string key) {
                int index = table.IndexOfName(key);
                if( index < 0 || index >= values.Length || values[index] is DeadValue )
                    return false;
                values[index] = DeadValue.Default;
                return true;
            }
            object IDictionary<string ,object>.this[string key] {
                get {
                    object val;
                    TryGetValue(key ,out val);
                    return val;
                }
                set {
                    SetValue(key ,value ,false);
                }
            }
            public object SetValue(string key ,object value) {
                return SetValue(key ,value ,false);
            }
            private object SetValue(string key ,object value ,bool isAdd) {
                if( key == null )
                    throw new ArgumentNullException("key");
                int index = table.IndexOfName(key);
                if( index < 0 ) {
                    index = table.AddField(key);
                } else if( isAdd && index < values.Length && !(values[index] is DeadValue) ) {
                    // then semantically, this value already exists
                    throw new ArgumentException("An item with the same key has already been added" ,"key");
                }
                int oldLength = values.Length;
                if( oldLength <= index ) {
                    // we'll assume they're doing lots of things, and
                    // grow it to the full width of the table
                    Array.Resize(ref values ,table.FieldCount);
                    for( int i = oldLength;i < values.Length;i++ ) {
                        values[i] = DeadValue.Default;
                    }
                }
                return values[index] = value;
            }
            ICollection<string> IDictionary<string ,object>.Keys {
                get {
                    return this.Select(kv => kv.Key).ToArray();
                }
            }
            ICollection<object> IDictionary<string ,object>.Values {
                get {
                    return this.Select(kv => kv.Value).ToArray();
                }
            }
            #endregion
        }
#endif
        private static Exception MultiMapException(IDataRecord reader) {
            bool hasFields = false;
            try {
                hasFields = reader != null && reader.FieldCount != 0;
            } catch { }
            if( hasFields )
                return new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id" ,"splitOn");
            else
                return new InvalidOperationException("No columns were selected");
        }
        internal static Func<IDataReader ,object> GetDapperRowDeserializer(IDataRecord reader ,int startBound ,int length ,bool returnNullIfFirstMissing) {
            var fieldCount = reader.FieldCount;
            if( length == -1 ) {
                length = fieldCount - startBound;
            }
            if( fieldCount <= startBound ) {
                throw MultiMapException(reader);
            }
            var effectiveFieldCount = Math.Min(fieldCount - startBound ,length);
            DapperTable table = null;
            return
                r => {
                    if( table == null ) {
                        string[] names = new string[effectiveFieldCount];
                        for( int i = 0;i < effectiveFieldCount;i++ ) {
                            names[i] = r.GetName(i + startBound);
                        }
                        table = new DapperTable(names);
                    }
                    var values = new object[effectiveFieldCount];
                    if( returnNullIfFirstMissing ) {
                        values[0] = r.GetValue(startBound);
                        if( values[0] is DBNull ) {
                            return null;
                        }
                    }
                    if( startBound == 0 ) {
                        for( int i = 0;i < values.Length;i++ ) {
                            object val = r.GetValue(i);
                            values[i] = val is DBNull ? null : val;
                        }
                    } else {
                        var begin = returnNullIfFirstMissing ? 1 : 0;
                        for( var iter = begin;iter < effectiveFieldCount;++iter ) {
                            object obj = r.GetValue(iter + startBound);
                            values[iter] = obj is DBNull ? null : obj;
                        }
                    }
                    return new DapperRow(table ,values);
                };
        }
        /// <summary>
        /// Internal use only
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
#if !DNXCORE50
        [Browsable(false)]
#endif
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("This method is for internal usage only" ,false)]
        public static char ReadChar(object value) {
            if( value == null || value is DBNull )
                throw new ArgumentNullException("value");
            string s = value as string;
            if( s == null || s.Length != 1 )
                throw new ArgumentException("A single-character was expected" ,"value");
            return s[0];
        }

#if !DNXCORE50
        [Browsable(false)]
#endif
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("This method is for internal usage only" ,false)]
        public static int ReadInt(object value) {
            if( value == null || value is DBNull ) {
                return 0;
            }

            long s = int.Parse(value.ToString());

            return ( int ) s;
        }
        /// <summary>
        /// Internal use only
        /// </summary>
#if !DNXCORE50
        [Browsable(false)]
#endif
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("This method is for internal usage only" ,false)]
        public static char? ReadNullableChar(object value) {
            if( value == null || value is DBNull )
                return null;
            string s = value as string;
            if( s == null || s.Length != 1 )
                throw new ArgumentException("A single-character was expected" ,"value");
            return s[0];
        }
        /// <summary>
        /// Internal use only
        /// </summary>
#if !DNXCORE50
        [Browsable(false)]
#endif
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("This method is for internal usage only" ,true)]
        public static IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters ,IDbCommand command ,string name) {
            IDbDataParameter result;
            if( parameters.Contains(name) ) {
                result = ( IDbDataParameter ) parameters[name];
            } else {
                result = command.CreateParameter();
                result.ParameterName = name;
                parameters.Add(result);
            }
            return result;
        }
        /// <summary>
        /// Internal use only
        /// </summary>
#if !DNXCORE50
        [Browsable(false)]
#endif
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("This method is for internal usage only" ,false)]
        public static void PackListParameters(IDbCommand command ,string namePrefix ,object value) {
            // initially we tried TVP, however it performs quite poorly.
            // keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare
            if( FeatureSupport.Get(command.Connection).Arrays ) {
                var arrayParm = command.CreateParameter();
                arrayParm.Value = SanitizeParameterValue(value);
                arrayParm.ParameterName = namePrefix;
                command.Parameters.Add(arrayParm);
            } else {
                var list = value as IEnumerable;
                var count = 0;
                bool isString = value is IEnumerable<string>;
                bool isDbString = value is IEnumerable<DbString>;
                foreach( var item in list ) {
                    count++;
                    var listParam = command.CreateParameter();
                    listParam.ParameterName = namePrefix + count;
                    if( isString ) {
                        listParam.Size = DbString.DefaultLength;
                        if( item != null && (( string ) item).Length > DbString.DefaultLength ) {
                            listParam.Size = -1;
                        }
                    }
                    if( isDbString && item as DbString != null ) {
                        var str = item as DbString;
                        str.AddParameter(command ,listParam.ParameterName);
                    } else {
                        listParam.Value = SanitizeParameterValue(item);
                        command.Parameters.Add(listParam);
                    }
                }
                var regexIncludingUnknown = @"([?@:]" + Regex.Escape(namePrefix) + @")(?!\w)(\s+(?i)unknown(?-i))?";
                if( count == 0 ) {
                    command.CommandText = Regex.Replace(command.CommandText ,regexIncludingUnknown ,match => {
                        var variableName = match.Groups[1].Value;
                        if( match.Groups[2].Success ) {
                            // looks like an optimize hint; leave it alone!
                            return match.Value;
                        } else {
                            return "(SELECT " + variableName + " WHERE 1 = 0)";
                        }
                    } ,RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant);
                    var dummyParam = command.CreateParameter();
                    dummyParam.ParameterName = namePrefix;
                    dummyParam.Value = DBNull.Value;
                    command.Parameters.Add(dummyParam);
                } else {
                    command.CommandText = Regex.Replace(command.CommandText ,regexIncludingUnknown ,match => {
                        var variableName = match.Groups[1].Value;
                        if( match.Groups[2].Success ) {
                            // looks like an optimize hint; expand it
                            var suffix = match.Groups[2].Value;
                            var sb = GetStringBuilder().Append(variableName).Append(1).Append(suffix);
                            for( int i = 2;i <= count;i++ ) {
                                sb.Append(',').Append(variableName).Append(i).Append(suffix);
                            }
                            return sb.__ToStringRecycle();
                        } else {
                            var sb = GetStringBuilder().Append('(').Append(variableName).Append(1);
                            for( int i = 2;i <= count;i++ ) {
                                sb.Append(',').Append(variableName).Append(i);
                            }
                            return sb.Append(')').__ToStringRecycle();
                        }
                    } ,RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant);
                }
            }
        }
        internal static object SanitizeParameterValue(object value) {
            if( value == null )
                return DBNull.Value;
            if( value is Enum ) {
                TypeCode typeCode;
                if( value is IConvertible ) {
                    typeCode = (( IConvertible ) value).GetTypeCode();
                } else {
                    typeCode = TypeExtensions.GetTypeCode(Enum.GetUnderlyingType(value.GetType()));
                }
                switch( typeCode ) {
                    case TypeCode.Byte:
                        return ( byte ) value;
                    case TypeCode.SByte:
                        return ( sbyte ) value;
                    case TypeCode.Int16:
                        return ( short ) value;
                    case TypeCode.Int32:
                        return ( int ) value;
                    case TypeCode.Int64:
                        return ( long ) value;
                    case TypeCode.UInt16:
                        return ( ushort ) value;
                    case TypeCode.UInt32:
                        return ( uint ) value;
                    case TypeCode.UInt64:
                        return ( ulong ) value;
                }
            }
            return value;
        }
        private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters ,string sql) {
            return parameters.Where(p => Regex.IsMatch(sql ,@"[?@:]" + p.Name + "([^a-z0-9_]+|$)" ,RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant));
        }
        // look for ? / @ / : *by itself*
        static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-z0-9@_])[?@:](?![a-z0-9@_])" ,RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
            literalTokens = new Regex(@"(?<![a-z0-9_])\{=([a-z0-9_]+)\}" ,RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
            pseudoPositional = new Regex(@"\?([a-z_][a-z0-9_]*)\?" ,RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
        /// <summary>
        /// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql
        /// </summary>
        internal struct LiteralToken {
            private readonly string token, member;
            /// <summary>
            /// The text in the original command that should be replaced
            /// </summary>
            public string Token {
                get {
                    return token;
                }
            }
            /// <summary>
            /// The name of the member referred to by the token
            /// </summary>
            public string Member {
                get {
                    return member;
                }
            }
            internal LiteralToken(string token ,string member) {
                this.token = token;
                this.member = member;
            }
            internal static readonly IList<LiteralToken> None = new LiteralToken[0];
        }
        /// <summary>
        /// Replace all literal tokens with their text form
        /// </summary>
        public static void ReplaceLiterals(this IParameterLookup parameters ,IDbCommand command) {
            var tokens = GetLiteralTokens(command.CommandText);
            if( tokens.Count != 0 )
                ReplaceLiterals(parameters ,command ,tokens);
        }
        internal static readonly MethodInfo format = typeof(SqlMapper).GetMethod("Format" ,BindingFlags.Public | BindingFlags.Static);
        /// <summary>
        /// Convert numeric values to their string form for SQL literal purposes
        /// </summary>
        [Obsolete("This is intended for internal usage only")]
        public static string Format(object value) {
            if( value == null ) {
                return "null";
            } else {
                switch( TypeExtensions.GetTypeCode(value.GetType()) ) {
#if !DNXCORE50
                    case TypeCode.DBNull:
                        return "null";
#endif
                    case TypeCode.Boolean:
                        return (( bool ) value) ? "1" : "0";
                    case TypeCode.Byte:
                        return (( byte ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.SByte:
                        return (( sbyte ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.UInt16:
                        return (( ushort ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.Int16:
                        return (( short ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.UInt32:
                        return (( uint ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.Int32:
                        return (( int ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.UInt64:
                        return (( ulong ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.Int64:
                        return (( long ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.Single:
                        return (( float ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.Double:
                        return (( double ) value).ToString(CultureInfo.InvariantCulture);
                    case TypeCode.Decimal:
                        return (( decimal ) value).ToString(CultureInfo.InvariantCulture);
                    default:
                        var multiExec = GetMultiExec(value);
                        if( multiExec != null ) {
                            StringBuilder sb = null;
                            bool first = true;
                            foreach( object subval in multiExec ) {
                                if( first ) {
                                    sb = GetStringBuilder().Append('(');
                                    first = false;
                                } else {
                                    sb.Append(',');
                                }
                                sb.Append(Format(subval));
                            }
                            if( first ) {
                                return "(select null where 1=0)";
                            } else {
                                return sb.Append(')').__ToStringRecycle();
                            }
                        }
                        throw new NotSupportedException(value.GetType().Name);
                }
            }
        }
        internal static void ReplaceLiterals(IParameterLookup parameters ,IDbCommand command ,IList<LiteralToken> tokens) {
            var sql = command.CommandText;
            foreach( var token in tokens ) {
                object value = parameters[token.Member];
#pragma warning disable 0618
                string text = Format(value);
#pragma warning restore 0618
                sql = sql.Replace(token.Token ,text);
            }
            command.CommandText = sql;
        }
        internal static IList<LiteralToken> GetLiteralTokens(string sql) {
            if( string.IsNullOrEmpty(sql) )
                return LiteralToken.None;
            if( !literalTokens.IsMatch(sql) )
                return LiteralToken.None;
            var matches = literalTokens.Matches(sql);
            var found = new HashSet<string>(StringComparer.Ordinal);
            List<LiteralToken> list = new List<LiteralToken>(matches.Count);
            foreach( Match match in matches ) {
                string token = match.Value;
                if( found.Add(match.Value) ) {
                    list.Add(new LiteralToken(token ,match.Groups[1].Value));
                }
            }
            return list.Count == 0 ? LiteralToken.None : list;
        }
    
        internal static Action<IDbCommand ,object> CreateParamInfoGenerator(Identity identity ,bool checkForDuplicates ,bool removeUnused ,IList<LiteralToken> literals) {
            Type type = identity.parametersType;
            bool filterParams = false;
            if( removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text ) {
                filterParams = !smellsLikeOleDb.IsMatch(identity.sql);
            }
            var dm = new DynamicMethod(string.Format("ParamInfo{0}" ,Guid.NewGuid()) ,null ,new[] { typeof(IDbCommand) ,typeof(object) } ,type ,true);
            var il = dm.GetILGenerator();
            bool isStruct = type.IsValueType();
            bool haveInt32Arg1 = false;
            il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
            if( isStruct ) {
                il.DeclareLocal(type.MakePointerType());
                il.Emit(OpCodes.Unbox ,type); // stack is now [typed-param]
            } else {
                il.DeclareLocal(type); // 0
                il.Emit(OpCodes.Castclass ,type); // stack is now [typed-param]
            }
            il.Emit(OpCodes.Stloc_0);// stack is now empty
            il.Emit(OpCodes.Ldarg_0); // stack is now [command]
            il.EmitCall(OpCodes.Callvirt ,typeof(IDbCommand).GetProperty("Parameters").GetGetMethod() ,null); // stack is now [parameters]
            var propsArr = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).ToArray();
            var ctors = type.GetConstructors();
            ParameterInfo[] ctorParams;
            IEnumerable<PropertyInfo> props = null;
            // try to detect tuple patterns, e.g. anon-types, and use that to choose the order
            // otherwise: alphabetical
            if( ctors.Length == 1 && propsArr.Length == (ctorParams = ctors[0].GetParameters()).Length ) {
                // check if reflection was kind enough to put everything in the right order for us
                bool ok = true;
                for( int i = 0;i < propsArr.Length;i++ ) {
                    if( !string.Equals(propsArr[i].Name ,ctorParams[i].Name ,StringComparison.OrdinalIgnoreCase) ) {
                        ok = false;
                        break;
                    }
                }
                if( ok ) {
                    // pre-sorted; the reflection gods have smiled upon us
                    props = propsArr;
                } else { // might still all be accounted for; check the hard way
                    var positionByName = new Dictionary<string ,int>(StringComparer.OrdinalIgnoreCase);
                    foreach( var param in ctorParams ) {
                        positionByName[param.Name] = param.Position;
                    }
                    if( positionByName.Count == propsArr.Length ) {
                        int[] positions = new int[propsArr.Length];
                        ok = true;
                        for( int i = 0;i < propsArr.Length;i++ ) {
                            int pos;
                            if( !positionByName.TryGetValue(propsArr[i].Name ,out pos) ) {
                                ok = false;
                                break;
                            }
                            positions[i] = pos;
                        }
                        if( ok ) {
                            Array.Sort(positions ,propsArr);
                            props = propsArr;
                        }
                    }
                }
            }
            if( props == null )
                props = propsArr.OrderBy(x => x.Name);
            if( filterParams ) {
                props = FilterParameters(props ,identity.sql);
            }
            var callOpCode = isStruct ? OpCodes.Call : OpCodes.Callvirt;
            foreach( var prop in props ) {
                if( typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType) ) {
                    il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
                    il.Emit(callOpCode ,prop.GetGetMethod()); // stack is [parameters] [custom]
                    il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command]
                    il.Emit(OpCodes.Ldstr ,prop.Name); // stack is now [parameters] [custom] [command] [name]
                    il.EmitCall(OpCodes.Callvirt ,prop.PropertyType.GetMethod("AddParameter") ,null); // stack is now [parameters]
                    continue;
                }
                ITypeHandler handler;
                DbType dbType = LookupDbType(prop.PropertyType ,prop.Name ,true ,out handler);
                if( dbType == DynamicParameters.EnumerableMultiParameter ) {
                    // this actually represents special handling for list types;
                    il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command]
                    il.Emit(OpCodes.Ldstr ,prop.Name); // stack is now [parameters] [command] [name]
                    il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param]
                    il.Emit(callOpCode ,prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
                    if( prop.PropertyType.IsValueType() ) {
                        il.Emit(OpCodes.Box ,prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value]
                    }
                    il.EmitCall(OpCodes.Call ,typeof(SqlMapper).GetMethod("PackListParameters") ,null); // stack is [parameters]
                    continue;
                }
                il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters]
                il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command]
                if( checkForDuplicates ) {
                    // need to be a little careful about adding; use a utility method
                    il.Emit(OpCodes.Ldstr ,prop.Name); // stack is now [parameters] [parameters] [command] [name]
                    il.EmitCall(OpCodes.Call ,typeof(SqlMapper).GetMethod("FindOrAddParameter") ,null); // stack is [parameters] [parameter]
                } else {
                    // no risk of duplicates; just blindly add
                    il.EmitCall(OpCodes.Callvirt ,typeof(IDbCommand).GetMethod("CreateParameter") ,null);// stack is now [parameters] [parameters] [parameter]
                    il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
                    il.Emit(OpCodes.Ldstr ,prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
                    il.EmitCall(OpCodes.Callvirt ,typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod() ,null);// stack is now [parameters] [parameters] [parameter]
                }
                if( dbType != DbType.Time && handler == null ) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time
                {
                    il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
                    if( dbType == DbType.Object && prop.PropertyType == typeof(object) ) // includes dynamic
                    {
                        // look it up from the param value
                        il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param]
                        il.Emit(callOpCode ,prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value]
                        il.Emit(OpCodes.Call ,typeof(SqlMapper).GetMethod("GetDbType" ,BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
                    } else {
                        // constant value; nice and simple
                        EmitInt32(il ,( int ) dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
                    }
                    il.EmitCall(OpCodes.Callvirt ,typeof(IDataParameter).GetProperty("DbType").GetSetMethod() ,null);// stack is now [parameters] [[parameters]] [parameter]
                }
                il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
                EmitInt32(il ,( int ) ParameterDirection.Input);// stack is now [parameters] [[parameters]] [parameter] [parameter] [dir]
                il.EmitCall(OpCodes.Callvirt ,typeof(IDataParameter).GetProperty("Direction").GetSetMethod() ,null);// stack is now [parameters] [[parameters]] [parameter]
                il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
                il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param]
                il.Emit(callOpCode ,prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value]
                bool checkForNull = true;
                if( prop.PropertyType.IsValueType() ) {
                    il.Emit(OpCodes.Box ,prop.PropertyType); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value]
                    if( Nullable.GetUnderlyingType(prop.PropertyType) == null ) {   // struct but not Nullable<T>; boxed value cannot be null
                        checkForNull = false;
                    }
                }
                if( checkForNull ) {
                    if( (dbType == DbType.String || dbType == DbType.AnsiString) && !haveInt32Arg1 ) {
                        il.DeclareLocal(typeof(int));
                        haveInt32Arg1 = true;
                    }
                    // relative stack: [boxed value]
                    il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value]
                    Label notNull = il.DefineLabel();
                    Label? allDone = (dbType == DbType.String || dbType == DbType.AnsiString) ? il.DefineLabel() : ( Label? ) null;
                    il.Emit(OpCodes.Brtrue_S ,notNull);
                    // relative stack [boxed value = null]
                    il.Emit(OpCodes.Pop); // relative stack empty
                    il.Emit(OpCodes.Ldsfld ,typeof(DBNull).GetField("Value")); // relative stack [DBNull]
                    if( dbType == DbType.String || dbType == DbType.AnsiString ) {
                        EmitInt32(il ,0);
                        il.Emit(OpCodes.Stloc_1);
                    }
                    if( allDone != null )
                        il.Emit(OpCodes.Br_S ,allDone.Value);
                    il.MarkLabel(notNull);
                    if( prop.PropertyType == typeof(string) ) {
                        il.Emit(OpCodes.Dup); // [string] [string]
                        il.EmitCall(OpCodes.Callvirt ,typeof(string).GetProperty("Length").GetGetMethod() ,null); // [string] [length]
                        EmitInt32(il ,DbString.DefaultLength); // [string] [length] [4000]
                        il.Emit(OpCodes.Cgt); // [string] [0 or 1]
                        Label isLong = il.DefineLabel(), lenDone = il.DefineLabel();
                        il.Emit(OpCodes.Brtrue_S ,isLong);
                        EmitInt32(il ,DbString.DefaultLength); // [string] [4000]
                        il.Emit(OpCodes.Br_S ,lenDone);
                        il.MarkLabel(isLong);
                        EmitInt32(il ,-1); // [string] [-1]
                        il.MarkLabel(lenDone);
                        il.Emit(OpCodes.Stloc_1); // [string] 
                    }
                    if( prop.PropertyType.FullName == LinqBinary ) {
                        il.EmitCall(OpCodes.Callvirt ,prop.PropertyType.GetMethod("ToArray" ,BindingFlags.Public | BindingFlags.Instance) ,null);
                    }
                    if( allDone != null )
                        il.MarkLabel(allDone.Value);
                    // relative stack [boxed value or DBNull]
                }
                if( handler != null ) {
#pragma warning disable 618
                    il.Emit(OpCodes.Call ,typeof(TypeHandlerCache<>).MakeGenericType(prop.PropertyType).GetMethod("SetValue")); // stack is now [parameters] [[parameters]] [parameter]
#pragma warning restore 618
                } else {
                    il.EmitCall(OpCodes.Callvirt ,typeof(IDataParameter).GetProperty("Value").GetSetMethod() ,null);// stack is now [parameters] [[parameters]] [parameter]
                }
                if( prop.PropertyType == typeof(string) ) {
                    var endOfSize = il.DefineLabel();
                    // don't set if 0
                    il.Emit(OpCodes.Ldloc_1); // [parameters] [[parameters]] [parameter] [size]
                    il.Emit(OpCodes.Brfalse_S ,endOfSize); // [parameters] [[parameters]] [parameter]
                    il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
                    il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size]
                    il.EmitCall(OpCodes.Callvirt ,typeof(IDbDataParameter).GetProperty("Size").GetSetMethod() ,null); // stack is now [parameters] [[parameters]] [parameter]
                    il.MarkLabel(endOfSize);
                }
                if( checkForDuplicates ) {
                    // stack is now [parameters] [parameter]
                    il.Emit(OpCodes.Pop); // don't need parameter any more
                } else {
                    // stack is now [parameters] [parameters] [parameter]
                    // blindly add
                    il.EmitCall(OpCodes.Callvirt ,typeof(IList).GetMethod("Add") ,null); // stack is now [parameters]
                    il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care
                }
            }
            // stack is currently [parameters]
            il.Emit(OpCodes.Pop); // stack is now empty
            if( literals.Count != 0 && propsArr != null ) {
                il.Emit(OpCodes.Ldarg_0); // command
                il.Emit(OpCodes.Ldarg_0); // command, command
                var cmdText = typeof(IDbCommand).GetProperty("CommandText");
                il.EmitCall(OpCodes.Callvirt ,cmdText.GetGetMethod() ,null); // command, sql
                Dictionary<Type ,LocalBuilder> locals = null;
                LocalBuilder local = null;
                foreach( var literal in literals ) {
                    // find the best member, preferring case-sensitive
                    PropertyInfo exact = null, fallback = null;
                    string huntName = literal.Member;
                    for( int i = 0;i < propsArr.Length;i++ ) {
                        string thisName = propsArr[i].Name;
                        if( string.Equals(thisName ,huntName ,StringComparison.OrdinalIgnoreCase) ) {
                            fallback = propsArr[i];
                            if( string.Equals(thisName ,huntName ,StringComparison.Ordinal) ) {
                                exact = fallback;
                                break;
                            }
                        }
                    }
                    var prop = exact ?? fallback;
                    if( prop != null ) {
                        il.Emit(OpCodes.Ldstr ,literal.Token);
                        il.Emit(OpCodes.Ldloc_0); // command, sql, typed parameter
                        il.EmitCall(callOpCode ,prop.GetGetMethod() ,null); // command, sql, typed value
                        Type propType = prop.PropertyType;
                        var typeCode = TypeExtensions.GetTypeCode(propType);
                        switch( typeCode ) {
                            case TypeCode.Boolean:
                                Label ifTrue = il.DefineLabel(), allDone = il.DefineLabel();
                                il.Emit(OpCodes.Brtrue_S ,ifTrue);
                                il.Emit(OpCodes.Ldstr ,"0");
                                il.Emit(OpCodes.Br_S ,allDone);
                                il.MarkLabel(ifTrue);
                                il.Emit(OpCodes.Ldstr ,"1");
                                il.MarkLabel(allDone);
                                break;
                            case TypeCode.Byte:
                            case TypeCode.SByte:
                            case TypeCode.UInt16:
                            case TypeCode.Int16:
                            case TypeCode.UInt32:
                            case TypeCode.Int32:
                            case TypeCode.UInt64:
                            case TypeCode.Int64:
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal:
                                // need to stloc, ldloca, call
                                // re-use existing locals (both the last known, and via a dictionary)
                                var convert = GetToString(typeCode);
                                if( local == null || local.LocalType != propType ) {
                                    if( locals == null ) {
                                        locals = new Dictionary<Type ,LocalBuilder>();
                                        local = null;
                                    } else {
                                        if( !locals.TryGetValue(propType ,out local) )
                                            local = null;
                                    }
                                    if( local == null ) {
                                        local = il.DeclareLocal(propType);
                                        locals.Add(propType ,local);
                                    }
                                }
                                il.Emit(OpCodes.Stloc ,local); // command, sql
                                il.Emit(OpCodes.Ldloca ,local); // command, sql, ref-to-value
                                il.EmitCall(OpCodes.Call ,InvariantCulture ,null); // command, sql, ref-to-value, culture
                                il.EmitCall(OpCodes.Call ,convert ,null); // command, sql, string value
                                break;
                            default:
                                if( propType.IsValueType() )
                                    il.Emit(OpCodes.Box ,propType); // command, sql, object value
                                il.EmitCall(OpCodes.Call ,format ,null); // command, sql, string value
                                break;
                        }
                        il.EmitCall(OpCodes.Callvirt ,StringReplace ,null);
                    }
                }
                il.EmitCall(OpCodes.Callvirt ,cmdText.GetSetMethod() ,null); // empty
            }
            il.Emit(OpCodes.Ret);
            return ( Action<IDbCommand ,object> ) dm.CreateDelegate(typeof(Action<IDbCommand ,object>));
        }
        static readonly Dictionary<TypeCode ,MethodInfo> toStrings = new[]
        {
            typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short),
            typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal)
        }.ToDictionary(x => TypeExtensions.GetTypeCode(x) ,x => x.GetPublicInstanceMethod("ToString" ,new[] { typeof(IFormatProvider) }));
        static MethodInfo GetToString(TypeCode typeCode) {
            MethodInfo method;
            return toStrings.TryGetValue(typeCode ,out method) ? method : null;
        }
        static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod("Replace" ,new Type[] { typeof(string) ,typeof(string) }),
            InvariantCulture = typeof(CultureInfo).GetProperty("InvariantCulture" ,BindingFlags.Public | BindingFlags.Static).GetGetMethod();
        
        private static Func<IDataReader ,object> GetStructDeserializer(Type type ,Type effectiveType ,int index) {
            // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!)
#pragma warning disable 618
            if( type == typeof(char) ) { // this *does* need special handling, though
                return r => SqlMapper.ReadChar(r.GetValue(index));
            }
            if( type == typeof(int) ) { // this *does* need special handling, though
                return r => SqlMapper.ReadInt(r.GetValue(index));
            }
            if( type == typeof(char?) ) {
                return r => SqlMapper.ReadNullableChar(r.GetValue(index));
            }
            if( type.FullName == LinqBinary ) {
                return r => Activator.CreateInstance(type ,r.GetValue(index));
            }
#pragma warning restore 618
            if( effectiveType.IsEnum() ) {   // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum
                return r => {
                    var val = r.GetValue(index);
                    if( val is float || val is double || val is decimal ) {
                        val = Convert.ChangeType(val ,Enum.GetUnderlyingType(effectiveType) ,CultureInfo.InvariantCulture);
                    }
                    return val is DBNull ? null : Enum.ToObject(effectiveType ,val);
                };
            }
             
            return r => {
                var val = r.GetValue(index);
                return val is DBNull ? null : val;
            };
        }
        private static T Parse<T>(object value) {
            if( value == null || value is DBNull )
                return default(T);
            if( value is T )
                return ( T ) value;
            var type = typeof(T);
            type = Nullable.GetUnderlyingType(type) ?? type;
            if( type.IsEnum() ) {
                if( value is float || value is double || value is decimal ) {
                    value = Convert.ChangeType(value ,Enum.GetUnderlyingType(type) ,CultureInfo.InvariantCulture);
                }
                return ( T ) Enum.ToObject(type ,value);
            }
            
            return ( T ) Convert.ChangeType(value ,type ,CultureInfo.InvariantCulture);
        }
        static readonly MethodInfo
                    enumParse = typeof(Enum).GetMethod("Parse" ,new Type[] { typeof(Type) ,typeof(string) ,typeof(bool) }),
                    getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
                        .Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int))
                        .Select(p => p.GetGetMethod()).First();
        /// <summary>
        /// Gets type-map for the given type
        /// </summary>
        /// <returns>Type map implementation, DefaultTypeMap instance if no override present</returns>
        public static ITypeMap GetTypeMap(Type type) {
            if( type == null )
                throw new ArgumentNullException("type");
#if DNXCORE50
            ITypeMap map = null;
#else
            var map = ( ITypeMap ) _typeMaps[type];
#endif
            if( map == null ) {
                lock( _typeMaps ) {   // double-checked; store this to avoid reflection next time we see this type
                    // since multiple queries commonly use the same domain-entity/DTO/view-model type
#if DNXCORE50
                    if (!_typeMaps.TryGetValue(type, out map)) map = null;
#else
                    map = ( ITypeMap ) _typeMaps[type];
#endif
                    if( map == null ) {
                        map = new DefaultTypeMap(type);
                        _typeMaps[type] = map;
                    }
                }
            }
            return map;
        }
        // use Hashtable to get free lockless reading
#if DNXCORE50
        private static readonly Dictionary<Type,ITypeMap> _typeMaps = new Dictionary<Type, ITypeMap>();
#else
        private static readonly Hashtable _typeMaps = new Hashtable();
#endif
        
        /// <summary>
        /// Internal use only
        /// </summary>
        /// <param name="type"></param>
        /// <param name="reader"></param>
        /// <param name="startBound"></param>
        /// <param name="length"></param>
        /// <param name="returnNullIfFirstMissing"></param>
        /// <returns></returns>
        public static Func<IDataReader ,object> GetTypeDeserializer(
#if CSHARP30
Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
#else
Type type ,IDataReader reader ,int startBound = 0 ,int length = -1 ,bool returnNullIfFirstMissing = false
#endif
) {
            var dm = new DynamicMethod(string.Format("Deserialize{0}" ,Guid.NewGuid()) ,typeof(object) ,new[] { typeof(IDataReader) } ,true);
            var il = dm.GetILGenerator();
            il.DeclareLocal(typeof(int));
            il.DeclareLocal(type);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Stloc_0);
            if( length == -1 ) {
                length = reader.FieldCount - startBound;
            }
            if( reader.FieldCount <= startBound ) {
                throw MultiMapException(reader);
            }
            var names = Enumerable.Range(startBound ,length).Select(i => reader.GetName(i)).ToArray();
            ITypeMap typeMap = GetTypeMap(type);
            int index = startBound;
            ConstructorInfo specializedConstructor = null;
#if !DNXCORE50
            bool supportInitialize = false;
#endif
            if( type.IsValueType() ) {
                il.Emit(OpCodes.Ldloca_S ,( byte ) 1);
                il.Emit(OpCodes.Initobj ,type);
            } else {
                var types = new Type[length];
                for( int i = startBound;i < startBound + length;i++ ) {
                    types[i - startBound] = reader.GetFieldType(i);
                }
                var explicitConstr = typeMap.FindExplicitConstructor();
                if( explicitConstr != null ) {
                    var structLocals = new Dictionary<Type ,LocalBuilder>();
                    var consPs = explicitConstr.GetParameters();
                    foreach( var p in consPs ) {
                        if( !p.ParameterType.IsValueType() ) {
                            il.Emit(OpCodes.Ldnull);
                        } else {
                            LocalBuilder loc;
                            if( !structLocals.TryGetValue(p.ParameterType ,out loc) ) {
                                structLocals[p.ParameterType] = loc = il.DeclareLocal(p.ParameterType);
                            }
                            il.Emit(OpCodes.Ldloca ,( short ) loc.LocalIndex);
                            il.Emit(OpCodes.Initobj ,p.ParameterType);
                            il.Emit(OpCodes.Ldloca ,( short ) loc.LocalIndex);
                            il.Emit(OpCodes.Ldobj ,p.ParameterType);
                        }
                    }
                    il.Emit(OpCodes.Newobj ,explicitConstr);
                    il.Emit(OpCodes.Stloc_1);
#if !DNXCORE50
                    supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
                    if( supportInitialize ) {
                        il.Emit(OpCodes.Ldloc_1);
                        il.EmitCall(OpCodes.Callvirt ,typeof(ISupportInitialize).GetMethod("BeginInit") ,null);
                    }
#endif
                } else {
                    var ctor = typeMap.FindConstructor(names ,types);
                    if( ctor == null ) {
                        string proposedTypes = "(" + string.Join(", " ,types.Select((t ,i) => t.FullName + " " + names[i]).ToArray()) + ")";
                        throw new InvalidOperationException(string.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization" ,proposedTypes ,type.FullName));
                    }
                    if( ctor.GetParameters().Length == 0 ) {
                        il.Emit(OpCodes.Newobj ,ctor);
                        il.Emit(OpCodes.Stloc_1);
#if !DNXCORE50
                        supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
                        if( supportInitialize ) {
                            il.Emit(OpCodes.Ldloc_1);
                            il.EmitCall(OpCodes.Callvirt ,typeof(ISupportInitialize).GetMethod("BeginInit") ,null);
                        }
#endif
                    } else {
                        specializedConstructor = ctor;
                    }
                }
            }
            il.BeginExceptionBlock();
            if( type.IsValueType() ) {
                il.Emit(OpCodes.Ldloca_S ,( byte ) 1);// [target]
            } else if( specializedConstructor == null ) {
                il.Emit(OpCodes.Ldloc_1);// [target]
            }
            var members = (specializedConstructor != null
                ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor ,n))
                : names.Select(n => typeMap.GetMember(n))).ToList();
            // stack is now [target]
            bool first = true;
            var allDone = il.DefineLabel();
            int enumDeclareLocal = -1, valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex;
            foreach( var item in members ) {
                if( item != null ) {
                    if( specializedConstructor == null )
                        il.Emit(OpCodes.Dup); // stack is now [target][target]
                    Label isDbNullLabel = il.DefineLabel();
                    Label finishLabel = il.DefineLabel();
                    il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
                    EmitInt32(il ,index); // stack is now [target][target][reader][index]
                    il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
                    il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
                    il.Emit(OpCodes.Callvirt ,getItem); // stack is now [target][target][value-as-object]
                    il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object]
                    StoreLocal(il ,valueCopyLocal);
                    Type colType = reader.GetFieldType(index);
                    Type memberType = item.MemberType;
                    if( memberType == typeof(char) || memberType == typeof(char?) ) {
                        il.EmitCall(OpCodes.Call ,typeof(SqlMapper).GetMethod(
                            memberType == typeof(char) ? "ReadChar" : "ReadNullableChar" ,BindingFlags.Static | BindingFlags.Public) ,null); // stack is now [target][target][typed-value]
                    } else {
                        il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
                        il.Emit(OpCodes.Isinst ,typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
                        il.Emit(OpCodes.Brtrue_S ,isDbNullLabel); // stack is now [target][target][value-as-object]
                        // unbox nullable enums as the primitive, i.e. byte etc
                        var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
                        var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum() ? nullUnderlyingType : memberType;
                        if( unboxType.IsEnum() ) {
                            Type numericType = Enum.GetUnderlyingType(unboxType);
                            if( colType == typeof(string) ) {
                                if( enumDeclareLocal == -1 ) {
                                    enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
                                }
                                il.Emit(OpCodes.Castclass ,typeof(string)); // stack is now [target][target][string]
                                StoreLocal(il ,enumDeclareLocal); // stack is now [target][target]
                                il.Emit(OpCodes.Ldtoken ,unboxType); // stack is now [target][target][enum-type-token]
                                il.EmitCall(OpCodes.Call ,typeof(Type).GetMethod("GetTypeFromHandle") ,null);// stack is now [target][target][enum-type]
                                LoadLocal(il ,enumDeclareLocal); // stack is now [target][target][enum-type][string]
                                il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
                                il.EmitCall(OpCodes.Call ,enumParse ,null); // stack is now [target][target][enum-as-object]
                                il.Emit(OpCodes.Unbox_Any ,unboxType); // stack is now [target][target][typed-value]
                            } else {
                                FlexibleConvertBoxedFromHeadOfStack(il ,colType ,unboxType ,numericType);
                            }
                            if( nullUnderlyingType != null ) {
                                il.Emit(OpCodes.Newobj ,memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
                            }
                        } else if( memberType.FullName == LinqBinary ) {
                            il.Emit(OpCodes.Unbox_Any ,typeof(byte[])); // stack is now [target][target][byte-array]
                            il.Emit(OpCodes.Newobj ,memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
                        } else {
                            TypeCode dataTypeCode = TypeExtensions.GetTypeCode(colType), unboxTypeCode = TypeExtensions.GetTypeCode(unboxType);
                            bool hasTypeHandler;
                            if( (hasTypeHandler =false) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType) ) {
                                if( hasTypeHandler ) {
#pragma warning disable 618
                                    il.EmitCall(OpCodes.Call ,typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod("Parse") ,null); // stack is now [target][target][typed-value]
#pragma warning restore 618
                                } else {
                                    il.Emit(OpCodes.Unbox_Any ,unboxType); // stack is now [target][target][typed-value]
                                }
                            } else {
                                // not a direct match; need to tweak the unbox
                                FlexibleConvertBoxedFromHeadOfStack(il ,colType ,nullUnderlyingType ?? unboxType ,null);
                                if( nullUnderlyingType != null ) {
                                    il.Emit(OpCodes.Newobj ,unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
                                }
                            }
                        }
                    }
                    if( specializedConstructor == null ) {
                        // Store the value in the property/field
                        if( item.Property != null ) {
                            if( type.IsValueType() ) {
                                il.Emit(OpCodes.Call ,DefaultTypeMap.GetPropertySetter(item.Property ,type)); // stack is now [target]
                            } else {
                                il.Emit(OpCodes.Callvirt ,DefaultTypeMap.GetPropertySetter(item.Property ,type)); // stack is now [target]
                            }
                        } else {
                            il.Emit(OpCodes.Stfld ,item.Field); // stack is now [target]
                        }
                    }
                    il.Emit(OpCodes.Br_S ,finishLabel); // stack is now [target]
                    il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
                    if( specializedConstructor != null ) {
                        il.Emit(OpCodes.Pop);
                        if( item.MemberType.IsValueType() ) {
                            int localIndex = il.DeclareLocal(item.MemberType).LocalIndex;
                            LoadLocalAddress(il ,localIndex);
                            il.Emit(OpCodes.Initobj ,item.MemberType);
                            LoadLocal(il ,localIndex);
                        } else {
                            il.Emit(OpCodes.Ldnull);
                        }
                    } else {
                        il.Emit(OpCodes.Pop); // stack is now [target][target]
                        il.Emit(OpCodes.Pop); // stack is now [target]
                    }
                    if( first && returnNullIfFirstMissing ) {
                        il.Emit(OpCodes.Pop);
                        il.Emit(OpCodes.Ldnull); // stack is now [null]
                        il.Emit(OpCodes.Stloc_1);
                        il.Emit(OpCodes.Br ,allDone);
                    }
                    il.MarkLabel(finishLabel);
                }
                first = false;
                index += 1;
            }
            if( type.IsValueType() ) {
                il.Emit(OpCodes.Pop);
            } else {
                if( specializedConstructor != null ) {
                    il.Emit(OpCodes.Newobj ,specializedConstructor);
                }
                il.Emit(OpCodes.Stloc_1); // stack is empty
#if !DNXCORE50
                if( supportInitialize ) {
                    il.Emit(OpCodes.Ldloc_1);
                    il.EmitCall(OpCodes.Callvirt ,typeof(ISupportInitialize).GetMethod("EndInit") ,null);
                }
#endif
            }
            il.MarkLabel(allDone);
            il.BeginCatchBlock(typeof(Exception)); // stack is Exception
            il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
            il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
            LoadLocal(il ,valueCopyLocal); // stack is Exception, index, reader, value
            il.EmitCall(OpCodes.Call ,typeof(SqlMapper).GetMethod("ThrowDataException") ,null);
            il.EndExceptionBlock();
            il.Emit(OpCodes.Ldloc_1); // stack is [rval]
            if( type.IsValueType() ) {
                il.Emit(OpCodes.Box ,type);
            }
            il.Emit(OpCodes.Ret);
            return ( Func<IDataReader ,object> ) dm.CreateDelegate(typeof(Func<IDataReader ,object>));
        }
        private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il ,Type from ,Type to ,Type via) {
            MethodInfo op;
            if( from == (via ?? to) ) {
                il.Emit(OpCodes.Unbox_Any ,to); // stack is now [target][target][typed-value]
            } else if( (op = GetOperator(from ,to)) != null ) {
                // this is handy for things like decimal <===> double
                il.Emit(OpCodes.Unbox_Any ,from); // stack is now [target][target][data-typed-value]
                il.Emit(OpCodes.Call ,op); // stack is now [target][target][typed-value]
            } else {
                bool handled = false;
                OpCode opCode = default(OpCode);
                switch( TypeExtensions.GetTypeCode(from) ) {
                    case TypeCode.Boolean:
                    case TypeCode.Byte:
                    case TypeCode.SByte:
                    case TypeCode.Int16:
                    case TypeCode.UInt16:
                    case TypeCode.Int32:
                    case TypeCode.UInt32:
                    case TypeCode.Int64:
                    case TypeCode.UInt64:
                    case TypeCode.Single:
                    case TypeCode.Double:
                        handled = true;
                        switch( TypeExtensions.GetTypeCode(via ?? to) ) {
                            case TypeCode.Byte:
                                opCode = OpCodes.Conv_Ovf_I1_Un;
                                break;
                            case TypeCode.SByte:
                                opCode = OpCodes.Conv_Ovf_I1;
                                break;
                            case TypeCode.UInt16:
                                opCode = OpCodes.Conv_Ovf_I2_Un;
                                break;
                            case TypeCode.Int16:
                                opCode = OpCodes.Conv_Ovf_I2;
                                break;
                            case TypeCode.UInt32:
                                opCode = OpCodes.Conv_Ovf_I4_Un;
                                break;
                            case TypeCode.Boolean: // boolean is basically an int, at least at this level
                            case TypeCode.Int32:
                                opCode = OpCodes.Conv_Ovf_I4;
                                break;
                            case TypeCode.UInt64:
                                opCode = OpCodes.Conv_Ovf_I8_Un;
                                break;
                            case TypeCode.Int64:
                                opCode = OpCodes.Conv_Ovf_I8;
                                break;
                            case TypeCode.Single:
                                opCode = OpCodes.Conv_R4;
                                break;
                            case TypeCode.Double:
                                opCode = OpCodes.Conv_R8;
                                break;
                            default:
                                handled = false;
                                break;
                        }
                        break;
                }
                if( handled ) {
                    il.Emit(OpCodes.Unbox_Any ,from); // stack is now [target][target][col-typed-value]
                    il.Emit(opCode); // stack is now [target][target][typed-value]
                    if( to == typeof(bool) ) { // compare to zero; I checked "csc" - this is the trick it uses; nice
                        il.Emit(OpCodes.Ldc_I4_0);
                        il.Emit(OpCodes.Ceq);
                        il.Emit(OpCodes.Ldc_I4_0);
                        il.Emit(OpCodes.Ceq);
                    }
                } else {
                    il.Emit(OpCodes.Ldtoken ,via ?? to); // stack is now [target][target][value][member-type-token]
                    il.EmitCall(OpCodes.Call ,typeof(Type).GetMethod("GetTypeFromHandle") ,null); // stack is now [target][target][value][member-type]
                    il.EmitCall(OpCodes.Call ,typeof(Convert).GetMethod("ChangeType" ,new Type[] { typeof(object) ,typeof(Type) }) ,null); // stack is now [target][target][boxed-member-type-value]
                    il.Emit(OpCodes.Unbox_Any ,to); // stack is now [target][target][typed-value]
                }
            }
        }
        static MethodInfo GetOperator(Type from ,Type to) {
            if( to == null )
                return null;
            MethodInfo[] fromMethods, toMethods;
            return ResolveOperator(fromMethods = from.GetMethods(BindingFlags.Static | BindingFlags.Public) ,from ,to ,"op_Implicit")
                ?? ResolveOperator(toMethods = to.GetMethods(BindingFlags.Static | BindingFlags.Public) ,from ,to ,"op_Implicit")
                ?? ResolveOperator(fromMethods ,from ,to ,"op_Explicit")
                ?? ResolveOperator(toMethods ,from ,to ,"op_Explicit");
        }
        static MethodInfo ResolveOperator(MethodInfo[] methods ,Type from ,Type to ,string name) {
            for( int i = 0;i < methods.Length;i++ ) {
                if( methods[i].Name != name || methods[i].ReturnType != to )
                    continue;
                var args = methods[i].GetParameters();
                if( args.Length != 1 || args[0].ParameterType != from )
                    continue;
                return methods[i];
            }
            return null;
        }
        private static void LoadLocal(ILGenerator il ,int index) {
            if( index < 0 || index >= short.MaxValue )
                throw new ArgumentNullException("index");
            switch( index ) {
                case 0:
                    il.Emit(OpCodes.Ldloc_0);
                    break;
                case 1:
                    il.Emit(OpCodes.Ldloc_1);
                    break;
                case 2:
                    il.Emit(OpCodes.Ldloc_2);
                    break;
                case 3:
                    il.Emit(OpCodes.Ldloc_3);
                    break;
                default:
                    if( index <= 255 ) {
                        il.Emit(OpCodes.Ldloc_S ,( byte ) index);
                    } else {
                        il.Emit(OpCodes.Ldloc ,( short ) index);
                    }
                    break;
            }
        }
        private static void StoreLocal(ILGenerator il ,int index) {
            if( index < 0 || index >= short.MaxValue )
                throw new ArgumentNullException("index");
            switch( index ) {
                case 0:
                    il.Emit(OpCodes.Stloc_0);
                    break;
                case 1:
                    il.Emit(OpCodes.Stloc_1);
                    break;
                case 2:
                    il.Emit(OpCodes.Stloc_2);
                    break;
                case 3:
                    il.Emit(OpCodes.Stloc_3);
                    break;
                default:
                    if( index <= 255 ) {
                        il.Emit(OpCodes.Stloc_S ,( byte ) index);
                    } else {
                        il.Emit(OpCodes.Stloc ,( short ) index);
                    }
                    break;
            }
        }
        private static void LoadLocalAddress(ILGenerator il ,int index) {
            if( index < 0 || index >= short.MaxValue )
                throw new ArgumentNullException("index");
            if( index <= 255 ) {
                il.Emit(OpCodes.Ldloca_S ,( byte ) index);
            } else {
                il.Emit(OpCodes.Ldloca ,( short ) index);
            }
        }
        /// <summary>
        /// Throws a data exception, only used internally
        /// </summary>
        [Obsolete("Intended for internal use only")]
        public static void ThrowDataException(Exception ex ,int index ,IDataReader reader ,object value) {
            Exception toThrow;
            try {
                string name = "(n/a)", formattedValue = "(n/a)";
                if( reader != null && index >= 0 && index < reader.FieldCount ) {
                    name = reader.GetName(index);
                    try {
                        if( value == null || value is DBNull ) {
                            formattedValue = "<null>";
                        } else {
                            formattedValue = Convert.ToString(value) + " - " + TypeExtensions.GetTypeCode(value.GetType());
                        }
                    } catch( Exception valEx ) {
                        formattedValue = valEx.Message;
                    }
                }
                toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})" ,index ,name ,formattedValue) ,ex);
            } catch { // throw the **original** exception, wrapped as DataException
                toThrow = new DataException(ex.Message ,ex);
            }
            throw toThrow;
        }
        private static void EmitInt32(ILGenerator il ,int value) {
            switch( value ) {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    break;
                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    break;
                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    break;
                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    break;
                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    break;
                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    break;
                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    break;
                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    break;
                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    break;
                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    break;
                default:
                    if( value >= -128 && value <= 127 ) {
                        il.Emit(OpCodes.Ldc_I4_S ,( sbyte ) value);
                    } else {
                        il.Emit(OpCodes.Ldc_I4 ,value);
                    }
                    break;
            }
        }
        /// <summary>
        /// Key used to indicate the type name associated with a DataTable
        /// </summary>
        private const string DataTableTypeNameKey = "dapper:TypeName";
        /// <summary>
        /// How should connection strings be compared for equivalence? Defaults to StringComparer.Ordinal.
        /// Providing a custom implementation can be useful for allowing multi-tenancy databases with identical
        /// schema to share strategies. Note that usual equivalence rules apply: any equivalent connection strings
        /// <b>MUST</b> yield the same hash-code.
        /// </summary>
        public static IEqualityComparer<string> ConnectionStringComparer {
            get {
                return connectionStringComparer;
            }
            set {
                connectionStringComparer = value ?? StringComparer.Ordinal;
            }
        }
        private static IEqualityComparer<string> connectionStringComparer = StringComparer.Ordinal;
        /// <summary>
        /// The grid reader provides interfaces for reading multiple result sets from a Dapper query 
        /// </summary>
        public partial class GridReader :IDisposable {
            private IDataReader reader;
            private IDbCommand command;
            private Identity identity;
           
            internal GridReader(IDbCommand command ,IDataReader reader ,Identity identity ,SqlMapper.IParameterCallbacks callbacks ) {
                this.command = command;
                this.reader = reader;
                this.identity = identity;
                this.callbacks = callbacks; 
            }
            
            /// <summary>
            /// Read the next grid of results
            /// </summary>
            public IEnumerable<T> Read<T>() {

                Type type= typeof(T);
                if (reader == null)
                    throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
                if (consumed)
                    throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
                var typedIdentity = identity.ForGrid(type, gridIndex);
                CacheInfo cache = GetCacheInfo(typedIdentity, null);
                var deserializer = cache.Deserializer;
                int hash = GetColumnHash(reader);
                if (deserializer.Func == null || deserializer.Hash != hash) {
                    deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
                    cache.Deserializer = deserializer;
                }
                consumed = true;
                IEnumerable<T> result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity);
                return result.ToList();
            }
             
           
            private IEnumerable<T> ReadDeferred<T>(int index ,Func<IDataReader ,object> deserializer ,Identity typedIdentity) {
                try {
                    while( index == gridIndex && reader.Read() ) {
                        var value = deserializer(reader);
                        yield return ( T ) value;
                    }
                } finally // finally so that First etc progresses things even when multiple rows
                  {
                    if( index == gridIndex ) {
                        NextResult();
                    }
                }
            }
            private int gridIndex, readCount;
            private bool consumed;
            private SqlMapper.IParameterCallbacks callbacks;
           
            private void NextResult() {
                if( reader.NextResult() ) {
                    readCount++;
                    gridIndex++;
                    consumed = false;
                } else {
                    // happy path; close the reader cleanly - no
                    // need for "Cancel" etc
                    reader.Dispose();
                    reader = null;
                    if( callbacks != null )
                        callbacks.OnCompleted();
                    Dispose();
                }
            }
            /// <summary>
            /// Dispose the grid, closing and disposing both the underlying reader and command.
            /// </summary>
            public void Dispose() {
                if( reader != null ) {
                    if( !reader.IsClosed && command != null )
                        command.Cancel();
                    reader.Dispose();
                    reader = null;
                }
                if( command != null ) {
                    command.Dispose();
                    command = null;
                }
            }
        }
         
         
        /// <summary>
        /// Fetch the type name associated with a DataTable
        /// </summary>
        public static string GetTypeName(this DataTable table) {
            return table == null ? null : table.ExtendedProperties[DataTableTypeNameKey] as string;
        }
        // one per thread
        [ThreadStatic]
        private static StringBuilder perThreadStringBuilderCache;
        private static StringBuilder GetStringBuilder() {
            var tmp = perThreadStringBuilderCache;
            if( tmp != null ) {
                perThreadStringBuilderCache = null;
                tmp.Length = 0;
                return tmp;
            }
            return new StringBuilder();
        }
        private static string __ToStringRecycle(this StringBuilder obj) {
            if( obj == null )
                return "";
            var s = obj.ToString();
            if( perThreadStringBuilderCache == null ) {
                perThreadStringBuilderCache = obj;
            }
            return s;
        }
    }
}
